﻿// Copyright (c) JosephZheng. All rights reserved. See License.txt in the project root for license

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using System.Text.RegularExpressions;

namespace Lyra.Cache.Server
{
    public class Compiler
    {
        private CodeDomProvider _provider = CodeDomProvider.CreateProvider("CSharp", new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
        private CompilerParameters _parameters = new CompilerParameters()
        {
            GenerateInMemory = true,
            GenerateExecutable = false,
            IncludeDebugInformation = false,
            TreatWarningsAsErrors = false,
            CompilerOptions = "/optimize"
        };

        public Assembly Assembly { get; private set; }
        public string Namespace { get; private set; }

        public Compiler(string id, string code)
        {
            string clearcode = ClearCode(code);
            clearcode = ProcessCode(id, clearcode);
            this.CopyReference();
            CompilerResults cr = _provider.CompileAssemblyFromSource(_parameters, clearcode);
            if (cr.Errors.HasErrors)
            {
                throw new Exception("compile error");
            }
            else
            {
                Assembly = cr.CompiledAssembly;
            }
        }

        public object GetInstance(string typeName)
        {
            return Assembly.CreateInstance(FormatTypeName(typeName));
        }

        public T InvokeMethod<T>(string typeName, string methodName, params object[] args)
        {
            string formattypename = FormatTypeName(typeName);
            Type type = Assembly.GetType(formattypename, true, true);
            return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, Assembly.CreateInstance(formattypename), args);
        }

        public T InvokeMethod<T>(object obj, string methodName, params object[] args)
        {
            return (T)obj.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, obj, args);
        }

        #region Internal
        private void CopyReference()
        {
            Assembly[] AssemblieLoadeds = AppDomain.CurrentDomain.GetAssemblies();

            foreach (Assembly assembly in AssemblieLoadeds)
            {
                if (!string.IsNullOrEmpty(assembly.Location) && assembly.Location.ToLower().EndsWith("dll"))
                {
                    _parameters.ReferencedAssemblies.Add(assembly.Location);
                }
            }

            _parameters.ReferencedAssemblies.Add(Path.Combine(System.Environment.CurrentDirectory, "Lyra.Cache.Extension.dll"));
        }

        private string ClearCode(string code)
        {
            string[] lines = code.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sb = new StringBuilder();
            foreach (string line in lines)
            {
                if (!line.TrimStart().StartsWith("//"))
                {
                    sb.Append(line);
                }
            }
            return sb.ToString();
        }

        private string ProcessCode(string id, string code)
        {
            List<string> usingstrs = new List<string>();
            StringBuilder sb = new StringBuilder();
            string temp = code;
            while (temp.Contains("using"))
            {
                int index = temp.IndexOf("using");
                sb.Append(temp.Substring(0, index));
                temp = temp.Substring(index);
                index = temp.IndexOf(";");
                string usingstring = temp.Substring(0, index + 1);
                if (!usingstrs.Contains(usingstring)) usingstrs.Add(usingstring);
                if (temp.Length > index + 1)
                {
                    temp = temp.Substring(index + 1);
                }
                else temp = null;
            }

            if (!string.IsNullOrWhiteSpace(temp)) sb.Append(temp);

            if (sb.ToString().Contains("namespace"))
            {
                temp = code.Substring(code.IndexOf("namespace") + 9);
                temp = temp.Substring(0, temp.IndexOf("{")).Trim();
                Namespace = string.Concat("NS", id, ".", temp);
                sb.Replace(temp, Namespace);
            }
            else
            {
                sb.Insert(0, string.Concat("namespace ", "NS", id, " {"));
                sb.Append("}");
                Namespace = id;
            }

            foreach (string usingstr in usingstrs)
            {
                sb.Insert(0, usingstr);
            }
            return sb.ToString();
        }

        private string FormatTypeName(string typeName)
        {
            if (!string.IsNullOrWhiteSpace(Namespace))
            {
                if (!typeName.StartsWith(Namespace))
                {
                    return string.Concat(Namespace, ".", typeName);
                }
            }
            return typeName;
        }
        #endregion

    }
}
